﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using DDD.Core;

namespace DDD.OpenTK {


    /// <summary>
    /// 3Dグラフィックス
    /// </summary>
    /// <remarks>
    /// <see cref="Graphics3D"/>はシーンを描画するグラフィックデバイス依存のクラスです。
    /// ユーザーはこのクラスの唯一のインスタンスを使用してシーンを描画します。
    /// 方法については(未定)
    /// </remarks>
    public static class Graphics3DExtensions {

        #region Field
        static CacheArray<int> glCache = new CacheArray<int> ();
        #endregion

        #region Constructor
        /// <summary>
        /// スタティック･コンストラクター
        /// </summary>
        static Graphics3DExtensions () {
            glCache.ReleaseEvents += new CacheArray<int>.ReleaseHandler (glCache_ReleaseEvents);
        }
        #endregion

        #region Method
        /// <summary>
        /// シーンをレンダリングします
        /// </summary>
        /// <remarks>
        /// シーンをレンダリングします。
        /// (*1) Deplicatedな機能だがNVIDIAのバグの回避のため呼び出している。
        /// これを指定しないとgl_PointCoordに値が入らない。
        /// </remarks>
        /// 
        /// <param name="g3d">Graphics3Dオブジェクト</param>
        public static void Render (this Graphics3D g3d) {

            var delegates = g3d.GetRenderPass ();
            foreach (var evnt in delegates) {

                // ---------------------------------------------
                // ユーザー定義のレンダリング設定の読み込み
                // ---------------------------------------------
                evnt.DynamicInvoke (null);

                // --------------------------------------------
                // 最終チェック
                // --------------------------------------------
                var vp = g3d.Viewport;
                if (vp.X == 0 && vp.Y == 0 && vp.Width == 0 && vp.Height == 0) {
                    throw new InvalidOperationException ("Viewport is (0,0,0,0)\n");
                }

                // ---------------------------------------------
                // フレームバッファーの切り替え
                // ---------------------------------------------
                var fbuf = g3d.FrameBuffer;
                if (fbuf == null) {
                    // デフォルトのフレームバッファー
                    GL.BindFramebuffer (FramebufferTarget.DrawFramebuffer, 0);
                    var color = g3d.ClearColor;
                    GL.ClearColor (color.R, color.G, color.B, color.A);
                    if (g3d.IsColorClearEnabled) {
                        GL.Clear (ClearBufferMask.ColorBufferBit);
                    }
                    if (g3d.IsDepthClearEnabled) {
                        GL.Clear (ClearBufferMask.DepthBufferBit);
                    }
                }
                else {
                    // オフライン・レンダリング
                    fbuf.Compile ();
                    fbuf.Validate ();
                    fbuf.Render ();
                }


                // ---------------------------------------------
                // 全体設定
                // ---------------------------------------------
                GL.Viewport (vp.X, vp.Y, vp.Width, vp.Height);
                GL.Enable (EnableCap.ProgramPointSize);
                GL.Enable (EnableCap.PointSprite);     // (*1)

                var wld = g3d.World;
                var selection = g3d.NodeSelection;
                var nodes = (selection == null) ? wld.Downwards : selection (wld);

                // ----------------------------------------------
                // シーンのコンパイル
                // ----------------------------------------------
                foreach (var node in nodes.Where (x => x.Is<Transformer> ())) {
                    var mesh = node.As<Transformer> ();
                    mesh.Compile ();
                }
                foreach (var node in nodes.Where (x => x.Is<Mesh> ())) {
                    var mesh = node.As<Mesh> ();
                    mesh.Compile ();
                }

                // ----------------------------------------------
                // シーンのレンダリング
                // ----------------------------------------------
                foreach (var node in nodes.Where (x => x.Is<Transformer> ())) {
                    var mesh = node.As<Transformer> ();
                    mesh.Render (node);
                }
                foreach (var node in nodes.Where (x => x.Is<Mesh> ())) {
                    var mesh = node.As<Mesh> ();
                    mesh.Render (node);
                }

           }
        }

        /// <summary>
        /// GLCacheを取得します
        /// </summary>
        /// <returns></returns>
        public static CacheArray<int> GetGLCache () {
            return glCache;
        }


        /// <summary>
        /// OpenGLリソースを開放します
        /// </summary>
        /// <remarks>
        /// manegedオブジェクトは全て自動でGCによって回収されますが､そこで使用されていたOpenGLリソースは自動では解放されません｡
        /// 通常このようなunmanagedリソースはユーザーが明示的にDispose()メソッドを呼び出して解放するのがC#の流儀ですが､
        /// DDDでは(めんどくさいので)全て自動で処理します｡
        /// ユーザーは定期的にこのメソッドを呼んで用済みになったOpenGLリソースを解放してください｡
        /// </remarks>
        public static void Release (this Graphics3D g3d) {
            glCache.Release ();
        }


        /// <summary>
        /// OpenGLリソースを開放します
        /// </summary>
        /// <remarks>
        /// このメソッドは<see cref="CacheArray{T}.ReleaseEvents"/>イベントにセットされ、
        /// GCによってOpenGLリソースが不要になったと判断されたときに呼び出されます。
        /// 第1引数の<paramref name="objectType"/>はリソースを保有していたオブジェクトの型です(インスタンスはGCによって回収済みです)｡
        /// 第2引数はOpenGLの識別し(int)です｡
        /// </remarks>
        /// (*1) この2つのクラスはジェネリック型が継承して使うので単純な比較ではtrueにならない｡
        /// <param name="objectType">リソースを保有していたオブジェクト</param>
        /// <param name="id">OpenGLリソース</param>
        static void glCache_ReleaseEvents (Type objectType, int id) {
            if (objectType == typeof (VertexBuffer)) {
                GL.DeleteVertexArrays (1, ref id);
                return;
            }
            if (objectType == typeof (VertexShader)) {
                GL.DeleteShader (id);
                return;
            }
            if (objectType == typeof (FragmentShader)) {
                GL.DeleteShader (id);
                return;
            }
            if (objectType == typeof (ShaderProgram)) {
                GL.DeleteProgram (id);
                return;
            }
            if (objectType == typeof (Image2D)) {
                GL.DeleteTexture (id);
                return;
            }
            if (objectType == typeof (Sampler)) {
                GL.DeleteSamplers (1, ref id);
                return;
            }
            if (objectType.IsSubclassOf (typeof (IndexBuffer))) {   // (*1)
                GL.DeleteBuffers (1, ref id);
                return;
            }
            if (objectType.IsSubclassOf (typeof (VertexArray))) {   // (*1)
                GL.DeleteBuffers (1, ref id);
                return;
            }

        }
        #endregion


    }
}
